#include "pch.h"
#include <assert.h>
#include "SerialCom.h"
#include "StringUtil.h"


// Constructor

CSerialCom::CSerialCom()
{
	m_hComm = NULL;
	m_ov.Offset = 0;
	m_ov.OffsetHigh = 0;
	m_ov.hEvent = NULL;
	m_hWriteEvent = NULL;
	m_hShutdownEvent = NULL;
	m_szWriteBuffer = NULL;
	m_nWriteSize = 1;
	m_bThreadAlive = FALSE;
}
//
// Delete dynamic memory
//
CSerialCom::~CSerialCom()
{
	do{
		SetEvent(m_hShutdownEvent);
	} while (m_bThreadAlive);

	TRACE("Thread ended/n");
	delete[] m_szWriteBuffer;
}
//
// Initialize the port. This can be port 1 to 4.
//
BOOL CSerialCom::InitPort(CWnd* pPortOwner, // the owner (CWnd) of the port (receives message)
	UINT  portnr,  // portnumber (1..8)
	UINT  baud,   // baudrate
	CHAR  parity,  // parity
	UINT  databits,  // databits
	UINT  stopbits,  // stopbits
	DWORD dwCommEvents, // EV_RXCHAR, EV_CTS etc
	UINT  writebuffersize) // size to the writebuffer
{
	//assert(portnr > 0 && portnr < 20);
	//assert(pPortOwner != NULL);
	// if the thread is alive: Kill
	if (m_bThreadAlive){
		do{
			SetEvent(m_hShutdownEvent);
		} while (m_bThreadAlive);
		TRACE("Thread ended/n");
	}
	// create events
	if (m_ov.hEvent != NULL)
		ResetEvent(m_ov.hEvent);
	m_ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	if (m_hWriteEvent != NULL)
		ResetEvent(m_hWriteEvent);
	m_hWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	if (m_hShutdownEvent != NULL)
		ResetEvent(m_hShutdownEvent);

	m_hShutdownEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	// initialize the event objects
	m_hEventArray[0] = m_hShutdownEvent; // highest priority
	m_hEventArray[1] = m_hWriteEvent;
	m_hEventArray[2] = m_ov.hEvent;
	// initialize critical section
	InitializeCriticalSection(&m_csCommunicationSync);
	// set buffersize for writing and save the owner
	m_pOwner = pPortOwner;
	if (m_szWriteBuffer != NULL)
		delete[] m_szWriteBuffer;
	m_szWriteBuffer = new char[writebuffersize];
	m_nPortNr = portnr;
	m_nWriteBufferSize = writebuffersize;
	m_dwCommEvents = dwCommEvents;
	BOOL bResult = FALSE;
	char *szPort = new char[50];
	char *szBaud = new char[50];
	// now it critical!
	EnterCriticalSection(&m_csCommunicationSync);
	// if the port is already opened: close it
	if (m_hComm != NULL)
	{
		CloseHandle(m_hComm);
		m_hComm = NULL;
	}
	// prepare port strings
	sprintf(szPort, "COM%d", portnr);
	sprintf(szBaud, "baud=%d parity=%c data=%d stop=%d", baud, parity, databits, stopbits);
	// get a handle to the port
	m_hComm = CreateFileA(szPort,      // communication port string (COMX)
		GENERIC_READ | GENERIC_WRITE, // read/write types
		0,        // comm devices must be opened with exclusive access
		NULL,       // no security attributes
		OPEN_EXISTING,     // comm devices must use OPEN_EXISTING
		FILE_FLAG_OVERLAPPED,   // Async I/O
		0);       // template must be 0 for comm devices

	if (m_hComm == INVALID_HANDLE_VALUE){
		delete[] szPort;
		delete[] szBaud;
		return FALSE;
	}
	// set the timeout values
	m_CommTimeouts.ReadIntervalTimeout = 2;
	m_CommTimeouts.ReadTotalTimeoutMultiplier = 0;
	m_CommTimeouts.ReadTotalTimeoutConstant = 0;
	m_CommTimeouts.WriteTotalTimeoutMultiplier = 2;
	m_CommTimeouts.WriteTotalTimeoutConstant = 500;
	// configure
	if (SetCommTimeouts(m_hComm, &m_CommTimeouts))
	{
		if (SetCommMask(m_hComm, dwCommEvents))
		{
			if (GetCommState(m_hComm, &m_dcb))
			{
				m_dcb.EvtChar = 'q';
				m_dcb.fRtsControl = RTS_CONTROL_ENABLE;  // set RTS bit high!
				if (BuildCommDCBA(szBaud, &m_dcb))
				{
					if (!SetCommState(m_hComm, &m_dcb))
						ProcessErrorMessage(L"SetCommState");
				}
				else
					ProcessErrorMessage(L"BuildCommDCB");
			}
			else
				ProcessErrorMessage(L"GetCommState");
		}
		else
			ProcessErrorMessage(L"SetCommMask");
	}
	else
		ProcessErrorMessage(L"SetCommTimeouts");

	delete[] szPort;
	delete[] szBaud;
	// flush the port
	PurgeComm(m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
	// release critical section
	LeaveCriticalSection(&m_csCommunicationSync);
	TRACE("Initialisation for communicationport %d completed./nUse Startmonitor to communicate./n", portnr);
	return TRUE;
}
//
//  The CommThread Function.
//
UINT CSerialCom::CommThread(LPVOID pParam)
{
	// Cast the void pointer passed to the thread back to
	// a pointer of CSerialCom class
	CSerialCom *port = (CSerialCom*)pParam;

	// Set the status variable in the dialog class to
	// TRUE to indicate the thread is running.
	port->m_bThreadAlive = TRUE;
	// Misc. variables
	DWORD BytesTransfered = 0;
	DWORD Event = 0;
	DWORD CommEvent = 0;
	DWORD dwError = 0;
	COMSTAT comstat;
	memset(&comstat, 0, sizeof(COMSTAT));
	BOOL  bResult = TRUE;

	// Clear comm buffers at startup
	if (port->m_hComm)  // check if the port is opened
		PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
	// begin forever loop.  This loop will run as long as the thread is alive.
	for (;;)
	{
		bResult = WaitCommEvent(port->m_hComm, &Event, &port->m_ov);
		if (!bResult)
		{
			// If WaitCommEvent() returns FALSE, process the last error to determin
			// the reason..
			switch (dwError = GetLastError()){
				case ERROR_IO_PENDING:{
					// This is a normal return value if there are no bytes
					// to read at the port.
					// Do nothing and continue
					break;
				}
				case 87:{
					// Under Windows NT, this value is returned for some reason.
					// I have not investigated why, but it is also a valid reply
					// Also do nothing and continue.
					break;
				}
				default:{
					// All other error codes indicate a serious error has
					// occured.  Process this error.
					port->ProcessErrorMessage(L"WaitCommEvent");
					break;
				}
			}
		}
		else
		{
			bResult = ClearCommError(port->m_hComm, &dwError, &comstat);
			if (comstat.cbInQue == 0)
				continue;
		} // end if bResult
		  // Main wait function.  This function will normally block the thread
		  // until one of nine events occur that require action.
		Event = WaitForMultipleObjects(3, port->m_hEventArray, FALSE, INFINITE);
		switch (Event){
			case 0:{
				CloseHandle(port->m_hComm);
				port->m_hComm = NULL;
				port->m_bThreadAlive = FALSE;
				AfxEndThread(200);
				break;
			}
			case 2:{ // read event
				GetCommMask(port->m_hComm, &CommEvent);
				if (CommEvent & EV_RXCHAR)
					ReceiveChar(port, comstat);// Receive character event from port.
				if (CommEvent & EV_CTS)
					::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_CTS_DETECTED, (WPARAM)0, (LPARAM)port->m_nPortNr);
				if (CommEvent & EV_BREAK)
					::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_BREAK_DETECTED, (WPARAM)0, (LPARAM)port->m_nPortNr);
				if (CommEvent & EV_ERR)
					::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_ERR_DETECTED, (WPARAM)0, (LPARAM)port->m_nPortNr);
				if (CommEvent & EV_RING)
					::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RING_DETECTED, (WPARAM)0, (LPARAM)port->m_nPortNr);
				if (CommEvent & EV_RXFLAG)
					::SendMessage(port->m_pOwner->m_hWnd, WM_COMM_RXFLAG_DETECTED, (WPARAM)0, (LPARAM)port->m_nPortNr);
				break;
			}
			case 1:{// write event
				// Write character event from port
				WriteChar(port);
				break;
			}
		} // end switch
	} // close forever loop
	return 0;
}
//
// start comm watching
//
BOOL CSerialCom::StartMonitoring()
{
	if (!(m_Thread = AfxBeginThread(CommThread, this)))
		return FALSE;
	TRACE("Thread started/n");
	return TRUE;
}
//
// Restart the comm thread
//
BOOL CSerialCom::RestartMonitoring()
{
	TRACE("Thread resumed/n");
	m_Thread->ResumeThread();
	return TRUE;
}

//
// Suspend the comm thread
//
BOOL CSerialCom::StopMonitoring()
{
	TRACE("Thread suspended/n");
	m_Thread->SuspendThread();
	return TRUE;
}

//
// If there is a error, give the right message
//
void CSerialCom::ProcessErrorMessage(wchar_t* ErrorText)
{
	//char *temp = new char[200];
	//LPVOID lpMsgBuf;
	//FormatMessage(
	//	FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
	//	NULL,
	//	GetLastError(),
	//	MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
	//	(LPTSTR)&lpMsgBuf,
	//	0,
	//	NULL
	//);
	//sprintf(temp, "WARNING:  %s Failed with the following error: /n%s/nPort: %d/n", ErrorText, (char *)(lpMsgBuf), m_nPortNr);
	//MessageBoxA(NULL, temp, "Application Error", MB_ICONSTOP);
	//LocalFree(lpMsgBuf);
	//delete[] temp;

	wchar_t temp[200] = { 0 };
	LPVOID lpMsgBuf;
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
		NULL,
		GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
		(LPTSTR)&lpMsgBuf,
		0,
		NULL
	);
	wsprintf(temp, L"%s Failed with the following error: %s\nPort: %d", ErrorText, (wchar_t *)(lpMsgBuf), m_nPortNr);
	MessageBox(NULL, temp, L"Application Error", MB_ICONSTOP);
	LocalFree(lpMsgBuf);
}
//
// Write a character.
//
void CSerialCom::WriteChar(CSerialCom* port)
{
	BOOL bWrite = TRUE;
	BOOL bResult = TRUE;
	DWORD BytesSent = 0;
	ResetEvent(port->m_hWriteEvent);
	// Gain ownership of the critical section
	EnterCriticalSection(&port->m_csCommunicationSync);
	if (bWrite)
	{
		// Initailize variables
		port->m_ov.Offset = 0;
		port->m_ov.OffsetHigh = 0;
		// Clear buffer
		PurgeComm(port->m_hComm, PURGE_RXCLEAR | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_TXABORT);
		bResult = WriteFile(port->m_hComm,	// Handle to COMM Port
			port->m_szWriteBuffer,			// Pointer to message buffer in calling finction
			(WORD)port->m_nWriteSize,		// Length of message to send
			&BytesSent,						// Where to store the number of bytes sent
			&port->m_ov);					// Overlapped structure, deal with any error codes

		if (!bResult)
		{
			DWORD dwError = GetLastError();
			switch (dwError)
			{
				case ERROR_IO_PENDING:
				{
					// continue to GetOverlappedResults()
					BytesSent = 0;
					bWrite = FALSE;
					break;
				}
				default:
				{
					port->ProcessErrorMessage(L"WriteFile");
				}
			}
		}else{
			LeaveCriticalSection(&port->m_csCommunicationSync);
		}
	} // end if(bWrite)
	if (!bWrite){
		bWrite = TRUE;

		bResult = GetOverlappedResult(port->m_hComm, // Handle to COMM port
			&port->m_ov,  // Overlapped structure
			&BytesSent,  // Stores number of bytes sent
			TRUE);    // Wait flag
		LeaveCriticalSection(&port->m_csCommunicationSync);
	} // end if (!bWrite)
	::SendMessage((port->m_pOwner)->m_hWnd, WM_COMM_TXEMPTY_DETECTED, 0, (LPARAM)port->m_nPortNr);
}
//
// Character received. Inform the owner
//

void CSerialCom::ReceiveChar(CSerialCom* port, COMSTAT comstat)
{
	BOOL  bRead = TRUE;
	BOOL  bResult = TRUE;
	DWORD dwError = 0;
	CReadData revData;
	for (;;)
	{
		EnterCriticalSection(&port->m_csCommunicationSync);
		bResult = ClearCommError(port->m_hComm, &dwError, &comstat);
		LeaveCriticalSection(&port->m_csCommunicationSync);
		if (comstat.cbInQue == 0){
			break;
		}
		EnterCriticalSection(&port->m_csCommunicationSync);
		if (bRead){
			memset(revData.revBuff, 0, BUF_LEN);
			bResult = ReadFile(port->m_hComm,  // Handle to COMM port
				&revData.revBuff,    // RX Buffer Pointer
				BUF_LEN,     // Read one byte
				&revData.size,   // Stores number of bytes read
				&port->m_ov);  // pointer to the m_ov structure

			if (!bResult){
				switch (dwError = GetLastError()){
					case ERROR_IO_PENDING:{
						// asynchronous i/o is still in progress
						// Proceed on to GetOverlappedResults();
						bRead = FALSE;
						break;
					}
					default:{
						// Another error has occured.  Process this error.
						port->ProcessErrorMessage(L"ReadFile");
						break;
					}
				}
			}else{
				// ReadFile() returned complete. It is not necessary to call GetOverlappedResults()
				bRead = TRUE;
			}
		}  // close if (bRead)
		if (!bRead)
		{
			bRead = TRUE;
			bResult = GetOverlappedResult(port->m_hComm, // Handle to COMM port
				&port->m_ov,		// Overlapped structure
				&revData.size,	// Stores number of bytes read
				TRUE);    // Wait flag

			if (!bResult)
			{// deal with the error code
				port->ProcessErrorMessage(L"GetOverlappedResults in ReadFile");
			}
		}  // close if (!bRead)
		//m_form_port
		LeaveCriticalSection(&port->m_csCommunicationSync);
		::SendMessage((port->m_pOwner)->m_hWnd, WM_COMM_REVDATA, (WPARAM)&revData, (LPARAM)port->m_nPortNr);
	} // end forever loop
}
//
// Write a string to the port
//
void CSerialCom::WriteToPort(char* string)
{
	assert(m_hComm != 0);
	memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
	strcpy(m_szWriteBuffer, string);
	m_nWriteSize = strlen(string);
	SetEvent(m_hWriteEvent);
}

void CSerialCom::WriteToPort(char* string, int n)
{
	assert(m_hComm != 0);
	EnterCriticalSection(&m_csCommunicationSync);
	memset(m_szWriteBuffer, 0, n);
	// memset(m_szWriteBuffer, 0, n);
	// strncpy(m_szWriteBuffer, string, n);
	memcpy(m_szWriteBuffer, string, n);
	// m_nWriteSize = n;
	// set event for write
	// SetEvent(m_hWriteEvent);

	BOOL bWrite = TRUE;
	BOOL bResult = TRUE;
	DWORD BytesSent = 0;
	bResult = WriteFile(m_hComm,m_szWriteBuffer,(WORD)n, &BytesSent,&m_ov);
	LeaveCriticalSection(&m_csCommunicationSync);
	if (!bResult){
		DWORD dwError = GetLastError();
		switch (dwError){
			case ERROR_IO_PENDING:{
				BytesSent = 0;
				bWrite = FALSE;
				break;
			}default:{
				ProcessErrorMessage(L"WriteFile");
			}
		}
	}
}


#if 0
void CSerialCom::WriteToPort(LPCTSTR string)
{
	assert(m_hComm != 0);
	memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
	strcpy(m_szWriteBuffer, string);
	m_nWriteSize = strlen(string);
	// set event for write
	SetEvent(m_hWriteEvent);
}
#endif

void CSerialCom::WriteToPort(LPCTSTR string, int n)
{
	assert(m_hComm != 0);
	memset(m_szWriteBuffer, 0, sizeof(m_szWriteBuffer));
	// strncpy(m_szWriteBuffer, string, n);
	memcpy(m_szWriteBuffer, string, n);
	m_nWriteSize = n;
	// set event for write
	SetEvent(m_hWriteEvent);
}
//
// Return the device control block
//
DCB CSerialCom::GetDCB()
{
	return m_dcb;
}
//
// Return the communication event masks
//
DWORD CSerialCom::GetCommEvents()
{
	return m_dwCommEvents;
}
//
// Return the output buffer size
//
DWORD CSerialCom::GetWriteBufferSize()
{
	return m_nWriteBufferSize;
}

void CSerialCom::ClosePort()
{
	SetEvent(m_hShutdownEvent);
}

void CSerialCom::EnumerateSerialPorts(CUIntArray& ports)
{
	ports.RemoveAll();    //Make sure we clear out any elements which may already be in the array
	OSVERSIONINFO osvi;
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	BOOL bGetVer = GetVersionEx(&osvi);

	//On NT use the QueryDosDevice API
	if (bGetVer && (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT))
	{
		//Use QueryDosDevice to look for all devices of the form COMx. This is a better
		//solution as it means that no ports have to be opened at all.
		TCHAR szDevices[65535];
		DWORD dwChars = QueryDosDevice(NULL, szDevices, 65535);
		if (dwChars) {
			int i = 0;
			for (;;) {
				TCHAR* pszCurrentDevice = &szDevices[i];    //Add the port number to the array which will be returned
															//If it looks like "COMX" then
															//add it to the array which will be returned
				size_t nLen = _tcslen(pszCurrentDevice);
				if (nLen > 3 && _tcsnicmp(pszCurrentDevice, _T("COM"), 3) == 0) {
					int nPort = _ttoi(&pszCurrentDevice[3]);    //Work out the port number
					ports.Add(nPort);        //clear out com_0
				}
				// Go to next NULL character
				while (szDevices[i] != _T('\0')) i++;
				i++;    // Bump pointer to the next string
						// The list is double-NULL terminated, so if the character is
						// now NULL, we're at the end
				if (szDevices[i] == _T('\0')) break;
			}
		}
		else TRACE(_T("Failed in call to QueryDosDevice, GetLastError:%d\n"), GetLastError());
	}
	else {
		//On 95/98 open up each port to determine their existence
		//Up to 255 COM ports are supported so we iterate through all of them seeing
		//if we can open them or if we fail to open them, get an access denied or general error error.
		//Both of these cases indicate that there is a COM port at that number.
		for (UINT i = 1; i < 256; i++)
		{
			//Form the Raw device name
			CString sPort;
			sPort.Format(_T("\\\\.\\COM%d"), i);

			//Try to open the port
			BOOL bSuccess = FALSE;
			HANDLE hPort = ::CreateFile(sPort, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
			if (hPort == INVALID_HANDLE_VALUE)
			{
				DWORD dwError = GetLastError();
				//Check to see if the error was because some other app had the port open or a general failure
				if (dwError == ERROR_ACCESS_DENIED || dwError == ERROR_GEN_FAILURE)
					bSuccess = TRUE;
			}
			else {
				bSuccess = TRUE;    //The port was opened successfully
				CloseHandle(hPort);    //Don't forget to close the port, since we are going to do nothing with it anyway
			}
			if (bSuccess) ports.Add(i);    //Add the port number to the array which will be returned
		}
	}
}
